<?php

namespace beObject\Primitive;

use beObject\Object;
use beObject\Utils\ArrayList;
use beObject\Exceptions\IllegalArgumentException;

/**
 * Object used to represent a string.
 * @author Dany Mottier
 * @copyright MD Consult
 * @license http://www.gnu.org/licenses/gpl.html
 * @link http://www.beobject-framework.net/beobject/primary/string/string-class/<br />
 * @package beObject\Primitive
 * @version 3.0
 */
class String
    extends Object {
  /** @var string A constant value that represents an empty string. */

  const __PTY = "";

  /** @var string The string to work on. */
  private $string;

  /**
   * Instantiate a string object with the given value.
   * @param string $string The string to work on.
   * @throws IllegalArgumentException if $string parameter is not a string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-__construct/
   */
  public function __construct($string = self::__PTY) {
    if (is_string($string)) {
      $this->string = $string;
    } else {
      throw new IllegalArgumentException("\$string parameter must be a string.");
    }
  }

  /**
   * This magic method is called when the current object is called like a
   * method.
   * @return integer The string value.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-__invoke/
   */
  public function __invoke() {
    return $this->string;
  }

  /**
   * This magic method is called when you output the variable with 'print' or 'echo'.
   * @return string The string representation of the inner string value.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-__tostring/
   */
  public function __toString() {
    return $this->toString();
  }

  /**
   * Returns the char at the specified index.
   * @param beObject\Primitive\Integer $index The index of the char to retrieve.
   * @return \beObject\Primitive\Char The indexed char.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-charat/
   */
  public function charAt(Integer $index) {
    return new Char($this->string[$index->integerValue()]);
  }

  /**
   * Binary safe string comparison.<br />
   * Note that this comparison is case sensitive.
   * @param beObject\Primitive\String $string The string to compare to.
   * @return beObject\Primitive\Integer Returns < 0 if inner string is less
   * than string parameter; > 0 if inner string is greater than string
   * parameter, and 0 if they are equal.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-compareto/
   */
  public function compareTo(String $string) {
    return new Integer(strcmp($this->string, $string));
  }

  /**
   * Binary safe string comparison.<br />
   * Note that this comparison is case insensitive.
   * @param beObject\Primitive\String $string The string to compare to.
   * @return beObject\Primitive\Integer Returns < 0 if inner string is less than string
   * parameter; > 0 if inner string is greater than string parameter,
   * and 0 if they are equal.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-comparetoignorecase/
   */
  public function compareToIgnoreCase(String $string) {
    return new Integer(strcasecmp($this->string, $string->toString()));
  }

  /**
   * Concatenate the given string to the inner string.
   * @param beObject\Primitive\String $string The string to concat.
   * @return beObject\Primitive\String The concatenated strings.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-concat/
   */
  public function concat(String $string) {
    return new String($this->string . $string->toString());
  }

  /**
   * Perform a regular expression match.
   * @param beObject\Primitive\String $string The pattern to search for.
   * @return beObject\Primitive\Boolean True if the pattern matches,
   * false otherwise.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-contains/
   */
  public function contains(String $string) {
    $match = preg_match($string->toString(), $this->string);

    if ($match !== false && $match != 0) {
      $return = new Boolean(true);
    } else {
      $return = new Boolean(false);
    }
    return $return;
  }

  /**
   * Check if the given string is the same as the inner string.
   * @param beObject\Primitive\String $string The string to compare to.
   * @return beObject\Primitive\Boolean True on success, false otherwise.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-contentequals/
   */
  public function contentEquals(String $string) {
    if (strlen($this->string) == $string->length()->integerValue()) {
      $testString = $string->toString();

      for ($i = 0; $i < $string->length()->integerValue(); ++$i) {
        if ($this->string[$i] != $testString[$i]) {
          return new Boolean(false);
        }
      }
      $return = new Boolean(true);
    } else {
      $return = new Boolean(false);
    }
    return $return;
  }

  /**
   * Concatenate each array index to the inner string.
   * @param array $array The array of string to concatenate.
   * @param beObject\Primitive\Integer $start The start index.
   * @param beObject\Primitive\Integer $stop The limit of the index to copy.
   * @return beObject\Primitive\String The concatenated string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-copyvalueof/
   */
  public static function copyValueOf(array $array, Integer $start = null,
                                     Integer $stop = null) {
    $tempString = String::__PTY;
    $tempStart = $start != null ? $start->integerValue() : 0;

    if ($stop != null && $tempStart + $stop() < count($array)) {
      $tempLength = $stop->integerValue();
    } else {
      $tempLength = count($array);
    }

    for ($i = $tempStart; $i < $tempLength; ++$i) {
      $tempString .= $array[$i];
    }
    return new String($tempString);
  }

  /**
   * Defines if a string ends with the given string.
   * @param beObject\Primitive\String $string The string to match.
   * @return beObject\Primitive\Boolean True if the inner strings ends with the
   * given string, false otherwise.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-endswith/
   */
  public function endsWith(String $string) {
    if (substr($this->string,
               strlen($this->string) - $string->length()->integerValue()) == $string->toString()) {
      return new Boolean(true);
    } else {
      return new Boolean(false);
    }
  }

  /**
   * Formats the string.
   * @param beObject\Primitive\String $string The string to format.
   * @param type $arg1 The first argument.
   * @param type $arg2 The second argument (default is null).
   * @param type $arg3 The third argument (default is null).
   * @param type $arg4 The fourth argument (default is null).
   * @param type $arg5 The fifth argument (default is null).
   * @return beObject\Primitive\String The format string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-format/
   */
  public static function format(String $string, $arg1, $arg2 = null,
                                $arg3 = null, $arg4 = null, $arg5 = null) {
    return new String(sprintf($string->toString(), $arg1, $arg2, $arg3, $arg4,
                              $arg5));
  }

  /**
   * Returns the first index of the given string.
   * @param beObject\Primitive\String $string The string to find.
   * @return beObject\Primitive\Integer The index of the searched string. If the
   * string is not found, -1 is returned.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-indexof/
   */
  public function indexOf(String $string) {
    $pos = strpos($this->string, $string->toString());

    return $pos === false ? new Integer(-1) : new Integer($pos);
  }

  /**
   * Defines if the string is empty or not.
   * @return \beObject\Primitive\Boolean True if the string is empty, false otherwise.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-isempty/
   */
  public function isEmpty() {
    return new Boolean(empty($this->string));
  }

  /**
   * Returns the length of the string.
   * @return beObject\Primitive\Integer The length of the string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-length/
   */
  public function length() {
    return new Integer(strlen($this->string));
  }

  /**
   * Returns the last index of the given string.
   * @param beObject\Primitive\String $needle The string to search for.
   * @return beObject\Primitive\Integer The last index of the searched string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-lastindexof/
   */
  public function lastIndexOf(String $needle) {
    $pos = strrpos($this->string, $needle->toString());

    return $pos === false ? new Integer(-1) : new Integer($pos);
  }

  /**
   * Pads a string at the left of the inner private property to reach the given length.
   * @param beObject\Primitive\String $char The string to pad.
   * @param beObject\Primitive\Integer $length The length to reach.
   * @return beObject\Primitive\String The padded string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-padleft/
   */
  public function padLeft(String $char, Integer $length) {
    return new String(str_pad($this->string, $length->integerValue(),
                              $char->toString(), STR_PAD_LEFT));
  }

  /**
   * Pads a string at the right of the inner private property to reach the given length.
   * @param beObject\Primitive\String $char The string to pad.
   * @param beObject\Primitive\Integer $length The length to reach.
   * @return beObject\Primitive\String The padded string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-padright/
   */
  public function padRight(String $char, Integer $length) {
    return new String(str_pad($this->string, $length->integerValue(), $char,
                              STR_PAD_RIGHT));
  }

  /**
   * Replace the pattern string by the replacement string.
   * @param beObject\Primitive\String $pattern The string to replace.
   * @param beObject\Primitive\String $replacement The replacement string.
   * @return beObject\Primitive\String The new string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-replace/
   */
  public function replace(String $pattern, String $replacement) {
    return new String(str_ireplace($pattern->toString(),
                                   $replacement->toString(), $this->string));
  }

  /**
   * Split a string by string.<br />
   * Returns an array of strings, each of which is a substring of string formed
   * by splitting it on boundaries formed by the string pattern.
   * @param beObject\Primitive\String $pattern The boundary string.
   * @param beObject\Primitive\Integer $limit If limit is set and positive, the returned array will
   * contain a maximum of limit elements with the last element containing the
   * rest of string.<br />If the limit parameter is negative, all components except
   * the last -limit are returned.<br />If the limit parameter is zero, then this is
   * treated as 1.
   * @return beObject\Utils\ArrayList Returns an array of strings created by
   * splitting the string parameter on boundaries formed by the delimiter.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-split/
   */
  public function split(String $pattern, Integer $limit = null) {
    if (!is_null($limit)) {
      return new ArrayList(explode($pattern(), $this->string, $limit()));
    } else {
      return new ArrayList(explode($pattern(), $this->string));
    }
  }

  /**
   * Defines if the string starts with the given string.
   * @param beObject\Primitive\String $string The string to match.
   * @return beObject\Primitive\Boolean True if the string match, false otherwise.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-startswith/
   */
  public function startsWith(String $string) {
    if (substr($this->string, 0, $string->length()->integerValue()) == $string) {
      return new Boolean(true);
    } else {
      return new Boolean(false);
    }
  }

  /**
   * Return part of a string.<br />
   * Returns the portion of string specified by the start and length parameters.
   * @param beObject\Primitive\Integer $start If start is non-negative, the returned string will
   * start at the start'th position in string, counting from zero.<br />
   * If start is negative, the returned string will start at the start'th
   * character from the end of string.
   * @param beObject\Primitive\Integer $length If length is given and is positive, the string
   * returned will contain at most length characters beginning from start
   * (depending on the length of string).<br />
   * If length is given and is negative, then that many characters will be
   * omitted from the end of string (after the start position has been
   * calculated when a start is negative). If start denotes the position of
   * this truncation or beyond, false will be returned.<br />
   * If length is omitted, the substring starting from start until the end of
   * the string will be returned.
   * @return beObject\Primitive\String Returns the extracted part of string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-substring/
   */
  public function subString(Integer $start, Integer $length = null) {
    if (!is_null($length)) {
      return new String(substr($this->string, $start->integerValue(),
                               $length->integerValue()));
    } else {
      return new String(substr($this->string, $start->integerValue()));
    }
  }

  /**
   * Put each character in the string to an array.
   * @return \beObject\Utils\ArrayList An array containing each character as a Char
   * @link http://www.beobject-framework.net/beobject/primary/string/string-tochararray/
   */
  public function toCharArray() {
    $sequence = array();

    for ($i = 0; $i < strlen($this->string); ++$i) {
      array_push($sequence, new Char($this->string[$i]));
    }
    return new ArrayList($sequence);
  }

  /**
   * Make a string lowercase.<br />
   * Returns string with all alphabetic characters converted to lowercase.<br />
   * Note that 'alphabetic' is determined by the current locale. This means
   * that in i.e. the default "C" locale, characters such as umlaut-A (Ä) will
   * not be converted.
   * @return beObject\Primitive\String Returns the lowercased string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-tolowercase/
   */
  public function toLowerCase() {
    return new String(strtolower($this->string));
  }

  /**
   * Returns the inner private property value.
   * @return string The inner private property value.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-tostring/
   */
  public function toString() {
    return $this->string;
  }

  /**
   * Make a string uppercase.<br />
   * Returns string with all alphabetic characters converted to uppercase.<br />
   * Note that 'alphabetic' is determined by the current locale. For instance,
   * in the default "C" locale characters such as umlaut-a (ä) will not be
   * converted.
   * @return beObject\Primitive\String Returns the uppercased string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-touppercase/
   */
  public function toUpperCase() {
    return new String(strtoupper($this->string));
  }

  /**
   * Strip whitespace (or other characters) from the beginning and end of a string.<br />
   * This function returns a string with whitespace stripped from the beginning
   * and end of the string. Without the second parameter, trim() will strip
   * these characters:
   * <ul>
   * <li>" " (ASCII 32 (0x20)), an ordinary space.</li>
   * <li>"\t" (ASCII 9 (0x09)), a tab.</li>
   * <li>"\n" (ASCII 10 (0x0A)), a new line (line feed).</li>
   * <li>"\r" (ASCII 13 (0x0D)), a carriage return.</li>
   * <li>"\0" (ASCII 0 (0x00)), the NUL-byte.</li>
   * <li>"\x0B" (ASCII 11 (0x0B)), a vertical tab.</li>
   * </ul>
   * @param beObject\Primitive\String $charlist Optionally, the stripped
   * characters can also be specified using the charlist parameter.
   * Simply list all characters that you want to be stripped. With .. you can
   * specify a range of characters.
   * @return beObject\Primitive\String The trimmed string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-trim/
   */
  public function trim(String $charlist = null) {
    if (!is_null($charlist)) {
      return new String(rtrim(ltrim($this->string, $charlist->toString()),
                                    $charlist->toString()));
    } else {
      return new String(ltrim(rtrim($this->string)));
    }
  }

  /**
   * Strip whitespace (or other characters) from the end of a string.<br />
   * This function returns a string with whitespace stripped from the end of the string.
   * Without the second parameter, trimEnd() will strip these characters:
   * <ul>
   * <li>" " (ASCII 32 (0x20)), an ordinary space.</li>
   * <li>"\t" (ASCII 9 (0x09)), a tab.</li>
   * <li>"\n" (ASCII 10 (0x0A)), a new line (line feed).</li>
   * <li>"\r" (ASCII 13 (0x0D)), a carriage return.</li>
   * <li>"\0" (ASCII 0 (0x00)), the NUL-byte.</li>
   * <li>"\x0B" (ASCII 11 (0x0B)), a vertical tab.</li>
   * </ul>
   * @param beObject\Primitive\String $charlist Optionally, the stripped
   * characters can also be specified using the charlist parameter.
   * Simply list all characters that you want to be stripped. With .. you can
   * specify a range of characters.
   * @return beObject\Primitive\String The trimmed string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-trimend/
   */
  public function trimEnd(String $charlist = null) {
    if (!is_null($charlist)) {
      return new String(rtrim($this->string, $charlist->toString()));
    } else {
      return new String(rtrim($this->string));
    }
  }

  /**
   * Strip whitespace (or other characters) from the beginning of a string.<br />
   * Strip whitespace (or other characters) from the beginning of a string.
   * Without the second parameter, trimStart() will strip these characters:
   * <ul>
   * <li>" " (ASCII 32 (0x20)), an ordinary space.</li>
   * <li>"\t" (ASCII 9 (0x09)), a tab.</li>
   * <li>"\n" (ASCII 10 (0x0A)), a new line (line feed).</li>
   * <li>"\r" (ASCII 13 (0x0D)), a carriage return.</li>
   * <li>"\0" (ASCII 0 (0x00)), the NUL-byte.</li>
   * <li>"\x0B" (ASCII 11 (0x0B)), a vertical tab.</li>
   * </ul>
   * @param beObject\Primitive\String $charlist Optionally, the stripped
   * characters can also be specified using the charlist parameter.
   * Simply list all characters that you want to be stripped. With .. you can
   * specify a range of characters.
   * @return beObject\Primitive\String The trimmed string.
   * @link http://www.beobject-framework.net/beobject/primary/string/string-trimstart/
   */
  public function trimStart(String $charlist = null) {
    if (!is_null($charlist)) {
      return new String(ltrim($this->string, $charlist->toString()));
    } else {
      return new String(ltrim($this->string));
    }
  }

}

?>
